Popular Searches
Popular Course Categories
Popular Courses

Understanding Structures, Classes, and Objects in Swift

Understanding Structures, Classes, and Objects in Swift

Object-Oriented Swift and Advanced Language Features

Understanding Structures, Classes, and Objects in Swift

Course: iOS App Development Training

Topic: Understanding Structures, Classes, and Objects in Swift

Course Link: iOS App Development Training at JustAcademy

Demo Link: Register for a Course Demo

JustAcademy Homepage: Visit JustAcademy


1. Introduction

Structures and classes are fundamental building blocks of Swift programming. They allow developers to create custom data types that combine properties and methods. Structures are value types, while classes are reference types. An instance of a class is traditionally called an object, although Swift commonly uses the term instance for both classes and structures.

In iOS development, these concepts help organize application data, represent users and products, manage shared resources, and build reusable components.

Learning Objectives

  • Understand types, instances, structures, classes, and objects.
  • Create custom structures and classes in Swift.
  • Use properties, methods, and initializers.
  • Understand value semantics and reference semantics.
  • Learn inheritance, encapsulation, and object identity.
  • Choose between structures and classes for common iOS use cases.
  • Apply these concepts in practical Swift programs.

2. Understanding Types and Instances

A type defines the properties and behavior that its instances can have. An instance is a specific value created from that type.

struct Student {
    var name: String
    var age: Int
}

let studentOne = Student(name: "Aarav", age: 20)
let studentTwo = Student(name: "Meera", age: 22)

print(studentOne.name) // Aarav
print(studentTwo.name) // Meera

Here, Student is the type, and studentOne and studentTwo are instances. Each instance stores its own values.

3. Understanding Structures in Swift

3.1 What Is a Structure?

A structure is a custom value type declared with the struct keyword. Structures are suitable for representing independent data such as coordinates, measurements, settings, and model records.

3.2 Creating a Structure

struct Product {
    var name: String
    var price: Double
    var category: String
}

let product = Product(
    name: "Headphones",
    price: 2499.0,
    category: "Electronics"
)

print(product.name)
print(product.price)
print(product.category)

Swift can provide a memberwise initializer for structures, allowing values to be supplied for stored properties when an instance is created.

3.3 Stored and Computed Properties

Stored properties keep values as part of an instance. Computed properties calculate a value when accessed.

struct Rectangle {
    var width: Double
    var height: Double

    var area: Double {
        width * height
    }
}

let rectangle = Rectangle(width: 10, height: 5)
print(rectangle.area) // 50.0

The width and height properties store data, while area calculates its result from those properties.

3.4 Modifying Structure Properties

Use var for a structure instance when its mutable properties need to change. A structure instance declared with let cannot have its variable properties modified.

struct UserProfile {
    var username: String
    var city: String
}

var profile = UserProfile(username: "swift_dev", city: "Mumbai")
profile.city = "Pune"

print(profile.city) // Pune

3.5 Methods in Structures

Methods define the behavior of a type. A structure method that modifies its instance must use the mutating keyword.

struct Counter {
    var value = 0

    mutating func increment() {
        value += 1
    }

    mutating func reset() {
        value = 0
    }
}

var counter = Counter()
counter.increment()
counter.increment()

print(counter.value) // 2

3.6 Initializers in Structures

An initializer sets up the initial state of an instance. A custom initializer can validate values or apply specific initialization rules.

struct BankAccount {
    var owner: String
    var balance: Double

    init(owner: String, openingBalance: Double) {
        self.owner = owner
        self.balance = max(0, openingBalance)
    }
}

let account = BankAccount(owner: "Riya", openingBalance: 5000)
print(account.balance) // 5000.0

The self keyword refers to the current instance. It helps distinguish instance properties from initializer parameters.

4. Understanding Classes in Swift

4.1 What Is a Class?

A class is a custom reference type declared with the class keyword. Classes support inheritance and reference identity. Multiple variables can refer to the same class instance.

4.2 Creating a Class

class Car {
    var brand: String
    var model: String

    init(brand: String, model: String) {
        self.brand = brand
        self.model = model
    }

    func displayDetails() {
        print("\(brand) \(model)")
    }
}

let car = Car(brand: "Toyota", model: "Camry")
car.displayDetails()

This class defines two properties, an initializer, and a method. The initializer ensures that the brand and model are supplied when the instance is created.

4.3 Properties and Methods in Classes

class Employee {
    var name: String
    var monthlySalary: Double

    var annualSalary: Double {
        monthlySalary * 12
    }

    init(name: String, monthlySalary: Double) {
        self.name = name
        self.monthlySalary = monthlySalary
    }

    func displaySalary() {
        print("Annual salary: \(annualSalary)")
    }
}

let employee = Employee(name: "Kabir", monthlySalary: 45000)
employee.displaySalary()

Like structures, classes can contain stored properties, computed properties, property observers, and methods.

4.4 Understanding Reference Behavior

Assigning a class instance to another variable copies the reference, not the underlying object. Both variables can access the same instance.

class Person {
    var name: String

    init(name: String) {
        self.name = name
    }
}

let personOne = Person(name: "Aarav")
let personTwo = personOne

personTwo.name = "Meera"

print(personOne.name) // Meera
print(personTwo.name) // Meera

Both references point to the same object, so the change made through personTwo is visible through personOne.

5. Understanding Objects

5.1 What Is an Object?

An object is traditionally an instance of a class. It combines state and behavior. State is represented by properties, while behavior is represented by methods.

For example, a vehicle object can store its current speed and provide methods to accelerate or brake.

class Vehicle {
    var speed: Int = 0

    func accelerate() {
        speed += 10
    }

    func brake() {
        speed = max(0, speed - 10)
    }
}

let vehicle = Vehicle()
vehicle.accelerate()
vehicle.accelerate()

print(vehicle.speed) // 20

vehicle.brake()
print(vehicle.speed) // 10

5.2 Main Object-Oriented Programming Principles

Principle Meaning Example
Encapsulation Combining data and behavior while controlling access. Keeping a password property private.
Abstraction Exposing essential operations while hiding implementation details. Providing a simple login method.
Inheritance Creating a class based on another class. An electric car inheriting from a vehicle class.
Polymorphism Allowing different types to provide different implementations of shared behavior. Overriding a superclass method.

Structures can also support encapsulation and abstraction. Swift protocols provide another way to share behavior across different types without requiring class inheritance.

6. Structures vs Classes

Feature Structure Class
Keyword struct class
Type category Value type Reference type
Assignment Produces independent value behavior Copies a reference to the same instance
Inheritance Cannot inherit from another class or structure Can inherit from a superclass
Mutating methods Required to modify the instance's value Not required for ordinary property mutation
Identity operators Not used for structure identity === and !==
Memberwise initializer Provided in applicable cases Not automatically provided
Deinitializer Not supported Supported
Typical uses Coordinates, settings, value models Shared resources, identity-based entities, class hierarchies

7. Value Types and Reference Types

7.1 Value Semantics

When a structure is assigned to another variable, the new value behaves independently. Changing the copy does not modify the original.

struct Point {
    var x: Int
    var y: Int
}

var pointA = Point(x: 10, y: 20)
var pointB = pointA

pointB.x = 100

print(pointA.x) // 10
print(pointB.x) // 100

7.2 Reference Semantics

Class instances have reference semantics. Different variables can refer to the same instance and observe changes made through any of those references.

class Settings {
    var theme: String

    init(theme: String) {
        self.theme = theme
    }
}

let settingsA = Settings(theme: "Light")
let settingsB = settingsA

settingsB.theme = "Dark"

print(settingsA.theme) // Dark

7.3 Comparing Class Identity

The === operator checks whether two class references point to the same instance. The !== operator checks whether they point to different instances.

class Device {
    var name: String

    init(name: String) {
        self.name = name
    }
}

let deviceA = Device(name: "iPhone")
let deviceB = deviceA
let deviceC = Device(name: "iPhone")

print(deviceA === deviceB) // true
print(deviceA === deviceC) // false

Although deviceA and deviceC have the same property value, they are separate objects. Identity is different from value equality, which is commonly checked using == when the type supports equality.

8. Inheritance in Swift Classes

Inheritance lets a subclass reuse and extend properties and methods from a superclass.

class Animal {
    var name: String

    init(name: String) {
        self.name = name
    }

    func makeSound() {
        print("The animal makes a sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("Woof!")
    }

    func fetch() {
        print("\(name) is fetching the ball")
    }
}

let dog = Dog(name: "Bruno")
dog.makeSound()
dog.fetch()

The Dog class inherits the name property from Animal and overrides the makeSound() method.

8.1 Using the override Keyword

The override keyword indicates that a subclass provides a new implementation of an inherited overridable member.

class Notification {
    func send() {
        print("Sending notification")
    }
}

class EmailNotification: Notification {
    override func send() {
        print("Sending email notification")
    }
}

let notification = EmailNotification()
notification.send()

8.2 Using super

The super keyword allows a subclass to call an inherited implementation when appropriate.

class BaseView {
    func render() {
        print("Rendering base view")
    }
}

class ProfileView: BaseView {
    override func render() {
        super.render()
        print("Rendering profile details")
    }
}

let view = ProfileView()
view.render()

9. Initializers and Deinitializers

9.1 Initializers

An initializer, declared with init, establishes an instance's initial state. Structures can receive a memberwise initializer, while classes do not automatically receive the same kind of memberwise initializer.

class Member {
    var name: String
    var membershipLevel: String

    init(name: String, membershipLevel: String) {
        self.name = name
        self.membershipLevel = membershipLevel
    }

    convenience init(name: String) {
        self.init(name: name, membershipLevel: "Basic")
    }
}

let member = Member(name: "Riya")
print(member.membershipLevel) // Basic

A convenience initializer offers an alternative way to create an instance and delegates to another initializer in the same class.

9.2 Deinitializers

A class can define a deinit method that runs when its instance is being deallocated. Structures do not support deinitializers.

class FileSession {
    init() {
        print("Session started")
    }

    deinit {
        print("Session ended")
    }
}

var session: FileSession? = FileSession()
session = nil

In this example, setting the optional reference to nil releases that reference. When no strong references remain, the instance can be deallocated and its deinitializer can run.

10. Encapsulation and Access Control

Swift provides access control to limit which parts of a program can read or modify properties and methods. Common access levels include private, fileprivate, internal, public, and open.

struct UserAccount {
    private var password: String
    private(set) var username: String

    init(username: String, password: String) {
        self.username = username
        self.password = password
    }

    func validatePassword(_ input: String) -> Bool {
        password == input
    }

    mutating func updateUsername(_ newUsername: String) {
        username = newUsername
    }
}

var account = UserAccount(
    username: "swift_user",
    password: "secure123"
)

print(account.validatePassword("secure123")) // true
account.updateUsername("ios_user")
print(account.username) // ios_user

The password is private, so it cannot be accessed directly from outside the type. The private(set) modifier allows reading the username while restricting direct modification through its setter.

11. Choosing Between Structures and Classes in iOS

Swift generally recommends using structures by default and choosing classes when reference identity, inheritance, or class-based interoperability is necessary.

Scenario Recommended Type Reason
Coordinates and measurements Structure Data behaves as an independent value.
App configuration Structure Copies can be changed independently.
Server response model Structure Local data can be represented as a value.
Shared connection manager Class Reference identity and shared lifecycle can matter.
Subclassable base component Class Supports class inheritance.
Objective-C interoperability requiring a class Class Some framework APIs expect class-based types.

12. Practical Project: User Profile Manager

Objective: Use a structure to represent user information and a class to manage a simple application session.

import Foundation

struct UserProfile {
    var name: String
    var city: String
    var age: Int

    func displayProfile() {
        print("Name: \(name)")
        print("City: \(city)")
        print("Age: \(age)")
    }
}

class UserSession {
    var isLoggedIn = false
    var username: String?

    func login(username: String) {
        self.username = username
        isLoggedIn = true
        print("\(username) logged in")
    }

    func logout() {
        isLoggedIn = false
        username = nil
        print("User logged out")
    }
}

var profile = UserProfile(
    name: "Meera",
    city: "Mumbai",
    age: 24
)

profile.displayProfile()

let session = UserSession()
session.login(username: profile.name)
print(session.isLoggedIn) // true

session.logout()
print(session.isLoggedIn) // false

Project Explanation

  1. UserProfile: Stores a user's name, city, and age as a value type.
  2. displayProfile(): Displays the stored profile information.
  3. UserSession: Represents session state using a reference type.
  4. login(): Updates the username and login status.
  5. logout(): Clears the session state.

This is a learning example. Real applications should implement authentication and session management using appropriate secure mechanisms.

13. Common Mistakes and Best Practices

Mistake Recommended Practice
Using classes for every model Prefer structures for independent value-based data.
Forgetting mutating in a structure method Mark methods that modify the structure's value with mutating.
Expecting structures to support inheritance Use protocol conformance or a class when class inheritance is required.
Confusing identity with equality Use === for class identity and == for value equality where implemented.
Sharing mutable objects unintentionally Manage references and object ownership deliberately.
Ignoring initialization requirements Use initializers that establish valid state before an instance is used.
Creating strong reference cycles Understand Automatic Reference Counting (ARC) and use weak or unowned references when appropriate.

14. Practice Exercises

  1. Create a Book structure with title, author, and price properties. Add a method to display its details.
  2. Create a BankAccount class with deposit and withdrawal methods.
  3. Copy a structure instance, modify the copy, and confirm that the original remains unchanged.
  4. Assign a class instance to two variables and verify that both references observe the same property changes.
  5. Create a base Shape class and subclasses such as Circle and Rectangle.
  6. Write a class with a designated initializer and a convenience initializer.
  7. Decide whether an app configuration, a network connection, and a product model should be structures or classes. Explain why.

15. Interview Questions and Answers

Q1. What is a structure in Swift?

A structure is a custom value type declared with struct. It can contain properties, methods, initializers, and protocol conformance.

Q2. What is a class in Swift?

A class is a custom reference type declared with class. It supports inheritance, identity, and deinitialization.

Q3. What is an object?

An object is traditionally an instance of a class. Swift also uses the general term instance for values created from structures and classes.

Q4. What is the difference between value types and reference types?

Value types behave as independent values when assigned or passed around. Reference types allow multiple references to point to the same instance.

Q5. Why is mutating used in structures?

The mutating keyword allows a structure method to modify its instance's stored properties or replace the value of self.

Q6. Can structures inherit from other structures?

No. Structures cannot inherit from classes or other structures, but they can adopt protocols and use extensions.

Q7. What is the difference between == and ===?

== checks equality when the type implements it. === checks whether two class references point to the same instance.

Q8. What is an initializer?

An initializer prepares an instance by setting its initial property values and satisfying initialization requirements.

Q9. What is a convenience initializer?

A convenience initializer is a secondary class initializer that delegates to another initializer in the same class.

Q10. What is a deinitializer?

A deinitializer, declared using deinit, runs when a class instance is being deallocated.

Q11. When should a class be preferred over a structure?

Use a class when shared identity, inheritance, deinitialization, or class-based framework interoperability is required.

Q12. Can a constant class reference modify an object's properties?

Yes. A constant reference cannot be reassigned, but the referenced object's mutable properties can still be changed when access rules permit it.

16. Quick Revision Checklist

  • Understand the difference between a type and an instance.
  • Use struct for value types and class for reference types.
  • Learn stored properties, computed properties, and methods.
  • Use initializers to establish valid instance state.
  • Remember mutating for structure methods that modify the instance.
  • Understand value copying versus reference sharing.
  • Use inheritance and override when class hierarchies are appropriate.
  • Distinguish identity from equality.
  • Prefer structures for independent data and classes when reference semantics are needed.
  • Practice these concepts in small Swift programs.

17. Conclusion

Structures, classes, and objects are important concepts in Swift and iOS development. Structures model independent values, while classes provide reference identity and inheritance. Both can group related data and behavior into reusable types.

Understanding their differences helps developers create maintainable data models, manage state safely, and work effectively with Apple frameworks. Practice with small Swift projects to develop confidence in choosing the right type for each requirement.

18. Learning Resources

Previous Next
whatsapp